<?php
/**
 * Copyright (c) 2020
 * 摘    要：Ansible
 * 作    者：san
 * 修改日期：2020.04.06
 */

namespace App\Library\AutoDs;

use App\Model\Project;
use ErrorException;

class Ansible extends Command
{
    /**
     * Ansible concurrent number
     *
     * @var int
     */
    public $ansibleFork = 10;

    /**
     * Ansible timeout
     *
     * @var int
     */
    public $ansibleTimeout = 600;

    /**
     * Ansible additional parameters for calling SSH
     *
     * @var string
     */
    public $ansibleSshArgs = 'ANSIBLE_SSH_ARGS=\'-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o CheckHostIP=false\'';


    /**
     * Test if ansible command is available
     */
    public function test()
    {
        $command = 'ansible --version';

        return $this->_runLocalCommand($command);
    }

    /**
     *  ping test whether ansible connects to the remote host correctly
     *
     * @param array $hosts
     * @throws ErrorException
     * @return bool
     */
    public function ping($hosts = [])
    {
        $ansibleHosts = $this->_getAnsibleHosts($hosts);

        if ($this->getConfig()->is_open_grey) {
            //灰度机器ping
            $commandGray = sprintf('%s ansible %s -u %s -m ping -i %s -f %d -T %d -vvv',
                $this->ansibleSshArgs,
                escapeshellarg($ansibleHosts),
                escapeshellarg($this->getConfig()->release_user),
                escapeshellarg(Project::getAnsibleGrayHostsFile()),
                $this->ansibleFork,
                $this->ansibleTimeout);
            return $this->_runLocalCommand($commandGray);

        } else {
            $command = sprintf('%s ansible %s -u %s -m ping -i %s -f %d -T %d -vvv',
                $this->ansibleSshArgs,
                escapeshellarg($ansibleHosts),
                escapeshellarg($this->getConfig()->release_user),
                escapeshellarg(Project::getAnsibleHostsFile()),
                $this->ansibleFork,
                $this->ansibleTimeout);

            return $this->_runLocalCommand($command);
        }
    }

    /**
     * 根据传入的 remote hosts 数组 生成 ansible 命令需要的 hosts 参数
     *
     * @param array $remoteHosts 不能在remoteHosts中传入 :端口
     * @return string
     */
    protected function _getAnsibleHosts($remoteHosts = [])
    {
        // ansible 多个主机通过 : 间隔
        if ($remoteHosts) {
            $ansibleHosts = implode(':', $remoteHosts);
        } else {
            $ansibleHosts = 'all';
        }

        return $ansibleHosts;
    }

    /**
     * 通过 ansible 并发执行目标机器命令
     *
     * @param string $command
     * @param array $hosts
     * @throws ErrorException
     * @return bool|int
     */
    public function runRemoteCommandByAnsibleRaw($remoteCommand, $hosts = [])
    {
        $ansibleHosts = $this->_getAnsibleHosts($hosts);

        $localCommand = sprintf('%s ansible %s -u %s -m raw -a %s -i %s -f %d -T %d',
            $this->ansibleSshArgs,
            escapeshellarg($ansibleHosts),
            escapeshellarg($this->getConfig()->release_user),
            escapeshellarg($remoteCommand),
            escapeshellarg(Project::getAnsibleHostsFile()),
            $this->ansibleFork,
            $this->ansibleTimeout);

        return $this->_runLocalCommand($localCommand);
    }

    /**
     * 通过 ansible 并发执行目标机器命令
     * Command 模块, 无法取得返回值, 不支持管道符, 命令中含有 $HOME, "<", ">", "|", and "&" 会返回失败
     *
     * @param $remoteCommand
     * @param array $hosts
     * @throws ErrorException
     * @return bool|int
     */
    public function runRemoteCommandByAnsibleCommand($remoteCommand, $hosts = [])
    {
        $ansibleHosts = $this->_getAnsibleHosts($hosts);

        $localCommand = sprintf('%s ansible %s -u %s -m command -a %s -i %s -f %d -T %d',
            $this->ansibleSshArgs,
            escapeshellarg($ansibleHosts),
            escapeshellarg($this->getConfig()->release_user),
            escapeshellarg($remoteCommand),
            escapeshellarg(Project::getAnsibleHostsFile()),
            $this->ansibleFork,
            $this->ansibleTimeout);

        return $this->_runLocalCommand($localCommand);
    }

    /**
     * 通过 ansible 并发执行目标机器命令
     * Shell 模块, 推荐使用
     *
     * @param $remoteCommand
     * @param array $hosts
     * @param bool $grayPush 是否灰度发布
     * @throws ErrorException
     * @return bool|int
     */
    public function runRemoteCommandByAnsibleShell($remoteCommand, $hosts = [], $grayPush = false)
    {
        $ansibleHosts = $this->_getAnsibleHosts($hosts);

        $hostsFile = $grayPush ? Project::getAnsibleGrayHostsFile() : Project::getAnsibleHostsFile();

        $localCommand = sprintf('%s ansible %s -u %s -m shell -a %s -i %s -f %d -T %d',
            $this->ansibleSshArgs,
            escapeshellarg($ansibleHosts),
            escapeshellarg($this->getConfig()->release_user),
            escapeshellarg($remoteCommand),
            escapeshellarg($hostsFile),
            $this->ansibleFork,
            $this->ansibleTimeout);

        return $this->_runLocalCommand($localCommand);
    }


    /**
     * 通过 ansible 并发执行目标机器命令
     * script 模块, 将宿主机的 .sh 文件推送到目标机上, 再执行这个文件
     *
     * @param $shellFile
     * @param array $hosts
     * @throws ErrorException
     * @return bool|int
     */
    public function runRemoteCommandByAnsibleScript($shellFile, $hosts = [])
    {
        $ansibleHosts = $this->_getAnsibleHosts($hosts);

        $localCommand = sprintf('%s ansible %s -u %s -m script -a %s -i %s -f %d -T %d',
            $this->ansibleSshArgs,
            escapeshellarg($ansibleHosts),
            escapeshellarg($this->getConfig()->release_user),
            escapeshellarg($shellFile),
            escapeshellarg(Project::getAnsibleHostsFile()),
            $this->ansibleFork,
            $this->ansibleTimeout);

        return $this->_runLocalCommand($localCommand);
    }

    /**
     * 通过 ansible 复制文件模块, 并发传输文件
     *
     * @param string $src 宿主机文件路径
     * @param string $dest 目标机文件路径
     * @param array $hosts 可选的主机列表
     * @param bool $isGray 是否灰度
     * @throws ErrorException
     * @return bool|int
     */
    public function copyFilesByAnsibleCopy(Project $project, $src, $dest, $hosts = [], $isGray = false)
    {
        $ansibleHosts = $this->_getAnsibleHosts($hosts);

        $localCommand = sprintf('%s ansible %s -u %s -m copy -a %s -i %s -f %d -T %d',
            $this->ansibleSshArgs,
            escapeshellarg($ansibleHosts),
            escapeshellarg($this->getConfig()->release_user),
            escapeshellarg('src=' . $src . ' dest=' . $dest),
            escapeshellarg($isGray ? $project::getAnsibleGrayHostsFile() : $project::getAnsibleHostsFile()),
            $this->ansibleFork,
            $this->ansibleTimeout);

        return $this->_runLocalCommand($localCommand);
    }
}
